/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.modules.beans;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Hashtable;
import java.util.Enumeration;
import java.beans.Introspector;
import java.beans.IntrospectionException;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import org.openide.nodes.Node;
import org.openide.src.ClassElement;
import org.openide.src.MethodElement;
import org.openide.src.FieldElement;
import org.openide.src.MethodParameter;
import org.openide.src.Type;
import org.openide.src.Identifier;
/** Analyses the ClassElement trying to find source code patterns i.e.
* properties or event sets;
*
* @author Petr Hrebejk
*/
public class PatternAnalyser extends Object implements Node.Cookie {
private static final int PROPERTIES_RESERVE = 11;
private static final String GET_PREFIX = "get"; // NOI18N
private static final String SET_PREFIX = "set"; // NOI18N
private static final String IS_PREFIX = "is"; // NOI18N
private static final String ADD_PREFIX = "add"; // NOI18N
private static final String REMOVE_PREFIX = "remove"; // NOI18N
/* Collections which are returned by getters/setters */
private ArrayList currentPropertyPatterns = new ArrayList();
private ArrayList currentIdxPropertyPatterns = new ArrayList();
private ArrayList currentEventSetPatterns = new ArrayList();
/* Temporary collections used for analysing */
/**
* @associates PropertyPattern
*/
private HashMap propertyPatterns;
/**
* @associates PropertyPattern
*/
private HashMap idxPropertyPatterns;
/**
* @associates EventSetPattern
*/
private HashMap eventSetPatterns;
private ClassElement classElement;
private boolean ignore;
/** Creates new analyser for ClassElement
*/
public PatternAnalyser( ClassElement classElement ) {
this.classElement = classElement;
}
public void analyzeAll() {
if ( ignore ) {
return;
}
int methodCount = classElement.getMethods().length;
propertyPatterns = new HashMap( methodCount / 2 + PROPERTIES_RESERVE );
idxPropertyPatterns = new HashMap(); // Initial size 11
eventSetPatterns = new HashMap(); // Initial size 11
// Analyse patterns
resolveMethods();
resolveFields();
// Compare old and new patterns to resolve changes
resolveChangesOfProperties();
resolveChangesOfIdxProperties();
resolveChangesOfEventSets();
}
void setIgnore( boolean ignore ) {
this.ignore = ignore;
}
/*
void resolvePropertyChanges( ) {
HashMap oldPropertyPatterns = new HashMap( propertyPatterns );
HashMap newPropertyPatterns = new HashMap();
// All compare levels
for( int level = 0; true; level++ ) {
// Go through
}
}
*/
public Collection getPropertyPatterns() {
return currentPropertyPatterns;
}
public Collection getIdxPropertyPatterns() {
return currentIdxPropertyPatterns;
}
public Collection getEventSetPatterns() {
return currentEventSetPatterns;
}
/** Gets the classelemnt of this pattern analyser */
public ClassElement getClassElement() {
return classElement;
}
/** This method analyses the ClassElement for "property patterns".
* The method is analogous to JavaBean Introspector methods for classes
* without a BeanInfo.
*/
public void resolveMethods() {
// First get all methods in classElement
MethodElement[] methods = classElement.getMethods();
// Temporary structures for analysing EventSets
Hashtable adds = new Hashtable();
Hashtable removes = new Hashtable();
// Analyze each method
for ( int i = 0; i < methods.length ; i++ ) {
MethodElement method = methods[i];
String name = method.getName().getName();
if ( name.startsWith( GET_PREFIX ) || name.startsWith( SET_PREFIX ) || name.startsWith( IS_PREFIX ) ) {
PropertyPattern pp = analyseMethodForProperties( method );
if ( pp != null )
addProperty( pp );
}
if ( name.startsWith( ADD_PREFIX ) || name.startsWith( REMOVE_PREFIX ) ) {
analyseMethodForEventSets( method, adds, removes );
}
}
// Resolve the temporay structures of event sets
// Now look for matching addFooListener+removeFooListener pairs.
Enumeration keys = adds.keys();
while (keys.hasMoreElements()) {
String compound = (String) keys.nextElement();
// Skip any "add" which doesn't have a matching remove // NOI18N
if (removes.get (compound) == null ) {
continue;
}
// Method name has to end in Listener
if (compound.indexOf( "Listener:" ) <= 0 ) { // NOI18N
continue;
}
/*
String listenerName = compound.substring( 0, compound.indexOf( ':' ) );
String eventName = Introspector.decapitalize( listenerName.substring( 0, listenerName.length() - 8 ));
*/
MethodElement addMethod = (MethodElement)adds.get(compound);
MethodElement removeMethod = (MethodElement)removes.get(compound);
Type argType = addMethod.getParameters()[0].getType();
// Check if the argument is a subtype of EventListener
//try {
//if (!Introspector.isSubclass( argType.toClass(), java.util.EventListener.class ) ) {
//if (!java.util.EventListener.class.isAssignableFrom( argType.toClass() ) ) {
if ( !isSubclass(
ClassElement.forName( argType.getClassName().getFullName() ),
ClassElement.forName( "java.util.EventListener" ) ) ) // NOI18N
continue;
/*
}
}
catch ( java.lang.ClassNotFoundException ex ) {
continue;
}
*/
EventSetPattern esp;
try {
esp = new EventSetPattern( this, addMethod, removeMethod );
}
catch ( IntrospectionException ex ) {
esp = null;
}
if (esp != null)
addEventSet( esp );
}
}
void resolveFields() {
// Analyze fields
FieldElement fields[] = classElement.getFields();
for ( int i = 0; i < fields.length; i++ ) {
FieldElement field=fields[i];
if ( ( field.getModifiers() & Modifier.STATIC ) != 0 )
continue;
PropertyPattern pp = (PropertyPattern)propertyPatterns.get( field.getName().getName() );
if ( pp == null )
pp = (PropertyPattern)idxPropertyPatterns.get( field.getName().getName() );
if ( pp == null )
continue;
Type ppType = pp.getType();
if ( ppType != null && pp.getType().compareTo( field.getType(), false ) )
pp.setEstimatedField( field );
}
}
private void resolveChangesOfProperties( ) {
currentPropertyPatterns = resolveChanges( currentPropertyPatterns, propertyPatterns, LevelComparator.PROPERTY );
}
private void resolveChangesOfIdxProperties( ) {
currentIdxPropertyPatterns = resolveChanges( currentIdxPropertyPatterns, idxPropertyPatterns, LevelComparator.IDX_PROPERTY );
}
private void resolveChangesOfEventSets() {
currentEventSetPatterns = resolveChanges( currentEventSetPatterns, eventSetPatterns, LevelComparator.EVENT_SET );
}
static ArrayList resolveChanges( Collection current, Map created, LevelComparator comparator ) {
ArrayList old = new ArrayList( current );
ArrayList cre = new ArrayList( created.size() );
cre.addAll( created.values() );
ArrayList result = new ArrayList( created.size() + 5 );
for ( int level = 0; level <= comparator.getLevels(); level ++ ) {
Iterator itCre = cre.iterator();
while ( itCre.hasNext() ) {
Pattern crePattern = (Pattern) itCre.next();
Iterator itOld = old.iterator();
while ( itOld.hasNext() ) {
Pattern oldPattern = (Pattern) itOld.next();
if ( comparator.compare( level, oldPattern, crePattern ) ) {
itOld.remove( );
itCre.remove( );
comparator.copyProperties(oldPattern, crePattern );
result.add( oldPattern );
break;
}
}
}
}
result.addAll( cre );
return result;
}
/** Analyses one method for property charcteristics */
PropertyPattern analyseMethodForProperties( MethodElement method ) {
// Skip static methods as Introspector does.
int modifiers = method.getModifiers();
if ( Modifier.isStatic( modifiers ) )
return null;
String name = method.getName().getName();
MethodParameter[] params = method.getParameters();
int paramCount = params == null ? 0 : params.length;
Type returnType = method.getReturn();
PropertyPattern pp = null;
try {
if ( paramCount == 0 ) {
if (name.startsWith( GET_PREFIX )) {
// SimpleGetter
pp = new PropertyPattern( this, method, null);
}
else if ( returnType.compareTo( Type.BOOLEAN, false ) && name.startsWith( IS_PREFIX )) {
// Boolean getter
pp = new PropertyPattern( this, method, null );
}
}
else if ( paramCount == 1 ) {
if ( params[0].getType().compareTo( Type.INT, false ) && name.startsWith( GET_PREFIX )) {
pp = new IdxPropertyPattern( this, null, null, method, null );
}
else if ( returnType.compareTo( Type.VOID, false ) && name.startsWith( SET_PREFIX )) {
pp = new PropertyPattern( this, null, method );
// PENDING vetoable => constrained
}
}
else if ( paramCount == 2 ) {
if ( params[0].getType().compareTo( Type.INT, false ) && name.startsWith( SET_PREFIX )) {
pp = new IdxPropertyPattern( this, null, null, null, method );
// PENDING vetoable => constrained
}
}
}
catch (IntrospectionException ex) {
// PropertyPattern constructor found some differencies from design patterns.
pp = null;
}
return pp;
}
/** Method analyses cass methods for EventSetPatterns
*/
void analyseMethodForEventSets( MethodElement method, Map adds, Map removes ) {
// Skip static methods
int modifiers = method.getModifiers();
if ( Modifier.isStatic( modifiers ) )
return;
String name = method.getName().getName();
MethodParameter params[] = method.getParameters();
Type returnType = method.getReturn();
if ( name.startsWith( ADD_PREFIX ) && params.length == 1 && returnType == Type.VOID ) {
String compound = name.substring(3) + ":" + params[0].getType(); // NOI18N
adds.put( compound, method );
}
else if ( name.startsWith( REMOVE_PREFIX ) && params.length == 1 && returnType == Type.VOID ) {
String compound = name.substring(6) + ":" + params[0].getType(); // NOI18N
removes.put( compound, method );
}
}
// Utility methods --------------------------------------------------------------------
/** Adds the new property. Or generates composite property if property
* of that name already exists. It puts the property in the right HashMep
* according to type of property idx || not idx
*/
private void addProperty( PropertyPattern pp ) {
boolean isIndexed = pp instanceof IdxPropertyPattern;
HashMap hm = isIndexed ? idxPropertyPatterns : propertyPatterns;
String name = pp.getName();
PropertyPattern old = (PropertyPattern)propertyPatterns.get(name);
if ( old == null )
old = (PropertyPattern)idxPropertyPatterns.get(name);
if (old == null) { // There is no other property of that name
hm.put(name, pp);
return;
}
// If the property type has changed, use new property pattern
Type opt = old.getType();
Type npt = pp.getType();
if ( opt != null && npt != null && !opt.compareTo(npt, false) ) {
hm.put( name, pp );
return;
}
PropertyPattern composite;
boolean isOldIndexed = old instanceof IdxPropertyPattern;
if (isIndexed || isOldIndexed ) {
if ( isIndexed && !isOldIndexed ) {
propertyPatterns.remove( old.getName() ); // Remove old from not indexed
}
else if ( !isIndexed && isOldIndexed ) {
idxPropertyPatterns.remove( old.getName() ); // Remove old from indexed
}
composite = new IdxPropertyPattern( old, pp );
idxPropertyPatterns.put( name, composite );
}
else {
composite = new PropertyPattern( old, pp );
propertyPatterns.put( name, composite );
}
// PENDING : Resolve types of getters and setters to pair correctly
// methods with equalNames.
/*
MethodElement getter = pp.getGetterMethod() == null ?
old.getGetterMethod() : pp.getGetterMethod();
MethodElement setter = pp.getSetterMethod() == null ?
old.getSetterMethod() : pp.getSetterMethod();
PropertyPattern composite = isIndexed ?
new IdxPropertyPattern ( getter, setter ) :
new PropertyPattern( getter, setter );
hm.put( pp.getName(), composite );
*/
}
/** adds an eventSetPattern */
void addEventSet( EventSetPattern esp ) {
String key = esp.getName() + esp.getType();
EventSetPattern old = (EventSetPattern)eventSetPatterns.get( key );
if ( old == null ) {
eventSetPatterns.put( key, esp);
return;
}
EventSetPattern composite = new EventSetPattern( old, esp );
eventSetPatterns.put( key, composite );
}
static boolean isSubclass(ClassElement a, ClassElement b) {
if (a == null || b == null) {
return false;
}
if (a.getName().compareTo( b.getName(), false ) ) {
return true;
}
for ( ClassElement x = a;
x != null;
x = x.getSuperclass() == null ? null : ClassElement.forName( x.getSuperclass().getFullName() ) ){
if (x.getName().compareTo( b.getName(), false ) ) {
return true;
}
if (b.isInterface()) {
Identifier interfaces[] = x.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
ClassElement interfaceElement = ClassElement.forName( interfaces[i].getFullName() );
if (isSubclass(interfaceElement, b)) {
return true;
}
}
}
}
return false;
}
// Inner Classes --- comparators for patterns -------------------------------------------------
abstract static class LevelComparator {
abstract boolean compare( int level, Pattern p1, Pattern p2 );
abstract int getLevels();
abstract void copyProperties( Pattern p1, Pattern p2 );
static LevelComparator PROPERTY = new LevelComparator.Property();
static LevelComparator IDX_PROPERTY = new LevelComparator.IdxProperty();
static LevelComparator EVENT_SET = new LevelComparator.EventSet();
static class Property extends LevelComparator {
boolean compare( int level, Pattern p1, Pattern p2 ) {
switch ( level ) {
case 0:
return ((PropertyPattern)p1).getGetterMethod() == ((PropertyPattern)p2).getGetterMethod() &&
((PropertyPattern)p1).getSetterMethod() == ((PropertyPattern)p2).getSetterMethod() ;
case 1:
return ((PropertyPattern)p1).getGetterMethod() == ((PropertyPattern)p2).getGetterMethod();
case 2:
return ((PropertyPattern)p1).getSetterMethod() == ((PropertyPattern)p2).getSetterMethod();
default:
return false;
}
}
int getLevels() {
return 2;
}
void copyProperties( Pattern p1, Pattern p2 ) {
((PropertyPattern) p1).copyProperties( (PropertyPattern)p2 );
}
}
static class IdxProperty extends LevelComparator {
boolean compare( int level, Pattern p1, Pattern p2 ) {
switch ( level ) {
case 0:
return ((IdxPropertyPattern)p1).getIndexedGetterMethod() == ((IdxPropertyPattern)p2).getIndexedGetterMethod() &&
((IdxPropertyPattern)p1).getIndexedSetterMethod() == ((IdxPropertyPattern)p2).getIndexedSetterMethod() ;
case 1:
return ((IdxPropertyPattern)p1).getIndexedGetterMethod() == ((IdxPropertyPattern)p2).getIndexedGetterMethod();
case 2:
return ((IdxPropertyPattern)p1).getIndexedSetterMethod() == ((IdxPropertyPattern)p2).getIndexedSetterMethod();
default:
return false;
}
}
int getLevels() {
return 2;
}
void copyProperties( Pattern p1, Pattern p2 ) {
((IdxPropertyPattern) p1).copyProperties( (IdxPropertyPattern)p2 );
}
}
static class EventSet extends LevelComparator {
boolean compare( int level, Pattern p1, Pattern p2 ) {
switch ( level ) {
case 0:
return ((EventSetPattern)p1).getAddListenerMethod() == ((EventSetPattern)p2).getAddListenerMethod() ||
((EventSetPattern)p1).getRemoveListenerMethod() == ((EventSetPattern)p2).getRemoveListenerMethod() ;
/*
case 1:
return ((EventSetPattern)p1).getAddListenerMethod() == ((EventSetPattern)p2).getAddListenerMethod();
case 2:
return ((EventSetPattern)p1).getRemoveListenerMethod() == ((EventSetPattern)p2).getRemoveListenerMethod();
*/
default:
return false;
}
}
int getLevels() {
return 0;
}
void copyProperties( Pattern p1, Pattern p2 ) {
((EventSetPattern) p1).copyProperties( (EventSetPattern)p2 );
}
}
}
}
/*
* Log
* 10 Gandalf 1.9 1/13/00 Petr Hrebejk i18n mk3
* 9 Gandalf 1.8 1/12/00 Petr Hrebejk i18n
* 8 Gandalf 1.7 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 7 Gandalf 1.6 8/2/99 Petr Hrebejk EventSetNode chilfren &
* EventSets types with src. code fixed
* 6 Gandalf 1.5 7/29/99 Petr Hrebejk Fix - change
* ReadOnly/WriteOnly to ReadWrite mode diddn't registered the added
* methods properly
* 5 Gandalf 1.4 7/26/99 Petr Hrebejk Better implementation of
* patterns resolving
* 4 Gandalf 1.3 7/21/99 Petr Hrebejk Field and Method
* listeners moved to PatternChildren
* 3 Gandalf 1.2 7/20/99 Ian Formanek compilable version
* 2 Gandalf 1.1 7/9/99 Petr Hrebejk Factory chaining fix
* 1 Gandalf 1.0 6/28/99 Petr Hrebejk
* $
*/